﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Tao.OpenGl;

namespace OhioState.Graphics.OpenGL
{
    internal class FrameBufferObjectGL : IFrameBufferObject, IOpenGLResource, IDisposable
    {
        #region IFrameBufferObject Members
        public IDepthBuffer DepthBuffer { get; private set; }

        public FrameBufferClear ClearFlags { get; private set; }

        public IList<ITexture> Textures
        {
            get { return new ReadOnlyCollection<ITexture>(textures); }
        }

        public void Enable(IRenderPanel panel)
        {
            if (guid == 0)
                CreateFrameBufferObject();
            Gl.glPushAttrib(Gl.GL_VIEWPORT_BIT | Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, guid);
            int[] buffers = new int[textures.Count];
            for (int attachment=0; attachment < textures.Count; attachment++)
                buffers[attachment] = Gl.GL_COLOR_ATTACHMENT0_EXT + attachment;
            Gl.glDrawBuffers(textures.Count, buffers);
            Gl.glViewport(0, 0, textures[0].Width, textures[0].Height);
            Clear(panel);
        }

        public void Disable(IRenderPanel panel)
        {
            Gl.glPopAttrib();
            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, 0);
        }
        #endregion

        #region Constructors
        public FrameBufferObjectGL(ICollection<ITexture> textures, FrameBufferClear clearFlags)
            : this(textures, null, clearFlags)
        {
        }

        public FrameBufferObjectGL(ICollection<ITexture> textures, IDepthBuffer depthBuffer, 
            FrameBufferClear clearFlags)
        {
            this.textures = new List<ITexture>(textures.Count);
            foreach (ITexture texture in textures)
            {
                this.textures.Add(texture);
            }
            this.DepthBuffer = depthBuffer;
            this.ClearFlags = clearFlags;
        }

        public FrameBufferObjectGL(ITexture texture, FrameBufferClear clearFlags)
            : this(texture, null, clearFlags)
        {
        }

        public FrameBufferObjectGL(ITexture texture, IDepthBuffer depthBuffer, FrameBufferClear clearFlags)
        {
            this.textures = new List<ITexture>(1);
            this.textures.Add(texture);
            this.DepthBuffer = depthBuffer;
            this.ClearFlags = clearFlags;
        }
        #endregion

        #region IHardwareResource<uint> Members
        public uint GUID
        {
            get
            {
                return guid;
            }
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            if (guid != 0)
            {
                Gl.glDeleteRenderbuffersEXT(1, ref guid);
            }
            GC.SuppressFinalize(this);
        }
        #endregion

        #region Implementation
        private void Clear(IRenderPanel panel)
        {
            int clearFlags = 0;
            if ((ClearFlags & FrameBufferClear.Color) == FrameBufferClear.Color)
            {
                Gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
                clearFlags |= Gl.GL_COLOR_BUFFER_BIT;
            }
            if ((ClearFlags & FrameBufferClear.Depth) == FrameBufferClear.Depth)
            {
                Gl.glClearDepth(1.0);
                clearFlags |= Gl.GL_DEPTH_BUFFER_BIT;
            }
            if (clearFlags != 0)
                Gl.glClear(clearFlags);
        }

        private void CreateFrameBufferObject()
        {
            Gl.glGenFramebuffersEXT(1, out guid);
            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, guid);
            int attachment = 0;
            foreach (ITexture texture in textures)
            {
                uint textureGuid = (texture as IOpenGLResource).GUID;
                Gl.glFramebufferTexture2DEXT(Gl.GL_FRAMEBUFFER_EXT, Gl.GL_COLOR_ATTACHMENT0_EXT + attachment, 
                    Gl.GL_TEXTURE_2D, textureGuid, 0);
                attachment++;
            }

            if (this.DepthBuffer != null)
            {
                uint depthGuid = (this.DepthBuffer as IOpenGLResource).GUID;
                Gl.glFramebufferRenderbufferEXT(Gl.GL_FRAMEBUFFER_EXT, Gl.GL_DEPTH_ATTACHMENT_EXT, Gl.GL_RENDERBUFFER_EXT, depthGuid);
            }
            int status = Gl.glCheckFramebufferStatusEXT(Gl.GL_FRAMEBUFFER_EXT);
            if (status != Gl.GL_FRAMEBUFFER_COMPLETE_EXT)
                throw new ApplicationException("Internal Error: The Frame Buffer Object (FBO) was not complete.");


            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, 0);
        }
        #endregion

        #region Member variables
        private IList<ITexture> textures;
        private uint guid = 0;
        #endregion
    }
}
